Passed
Branch v10.3.x (79db34)
by Rafael S.
02:52
created

interpolator.js ➔ lanczosWindow_   A

Complexity

Conditions 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2019 Rafael da Silva Rocha.
3
 * Copyright 2012 Spencer Cohen
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining
6
 * a copy of this software and associated documentation files (the
7
 * "Software"), to deal in the Software without restriction, including
8
 * without limitation the rights to use, copy, modify, merge, publish,
9
 * distribute, sublicense, and/or sell copies of the Software, and to
10
 * permit persons to whom the Software is furnished to do so, subject to
11
 * the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be
14
 * included in all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 *
24
 */
25
26
/**
27
 * @fileoverview The Interpolator class. Based on Smooth.js by Spencer Cohen.
28
 * @see https://github.com/rochars/wavefile
29
 * @see https://github.com/osuushi/Smooth.js
30
 */
31
32
/**
33
 * A class to get scaled values out of arrays.
34
 * @extends WaveFileReader
35
 */
36
export class Interpolator {
37
  
38
  /**
39
   * @param {number} scaleFrom the length of the original array.
40
   * @param {number} scaleTo The length of the new array.
41
   * @param {?Object} details The extra configuration, if needed.
42
   */
43
  constructor(scaleFrom, scaleTo, details) {
44
    /**
45
     * The length of the original array.
46
     * @type {number}
47
     */
48
    this.length_ = scaleFrom;
49
    /**
50
     * The scaling factor.
51
     * @type {number}
52
     */
53
    this.scaleFactor_ = (scaleFrom - 1) / scaleTo;
54
    /**
55
     * The interpolation function.
56
     * @type {Function}
57
     */
58
    this.interpolate = this.sinc;
59
    if (details.method === 'point') {
60
    	this.interpolate = this.point;
61
    } else if(details.method === 'linear') {
62
    	this.interpolate = this.linear;
63
    } else if(details.method === 'cubic') {
64
    	this.interpolate = this.cubic;
65
    }
66
    /**
67
     * The clipping function.
68
     * @type {Function}
69
     */
70
    this.clip_ = clipClamp_;
71
    // The clip function
72
    if (details.clip === 'periodic') {
73
      this.scaleFactor_ = scaleFrom / scaleTo;
74
      this.clip_ = clipPeriodic_;
75
    } else if (details.clip === 'mirror') {
76
      this.clip_ = clipMirror_;
77
    }
78
    /**
79
     * The tanget factor for cubic interpolation.
80
     * @type {number}
81
     */
82
    this.tangentFactor_ = 1 - Math.max(0, Math.min(1, details.tension || 0));
83
    // Configure the kernel for sinc
84
    /**
85
     * The sinc filter size.
86
     * @type {number}
87
     */
88
    this.sincFilterSize_ = details.sincFilterSize || 1;
89
    /**
90
     * The sinc kernel.
91
     * @type {Function}
92
     */
93
    this.kernel_ = sincKernel_(details.sincWindow || gaussianWindow_);
94
  }
95
96
  /**
97
   * @param {number} t The index to interpolate.
98
   * @param {Array|TypedArray} samples the original array.
99
   * @return {number} The interpolated value.
100
   */
101
  point(t, samples) {
102
    return this.getClippedInput_(Math.round(this.scaleFactor_ * t), samples);
103
  }
104
105
  /**
106
   * @param {number} t The index to interpolate.
107
   * @param {Array|TypedArray} samples the original array.
108
   * @return {number} The interpolated value.
109
   */
110
  linear(t, samples) {
111
    t = this.scaleFactor_ * t;
112
    let k = Math.floor(t);
113
    t -= k;
114
    return (1 - t) *
115
    	this.getClippedInput_(k, samples) + t *
116
    	this.getClippedInput_(k + 1, samples);
117
  }
118
119
  /**
120
   * @param {number} t The index to interpolate.
121
   * @param {Array|TypedArray} samples the original array.
122
   * @return {number} The interpolated value.
123
   */
124
  cubic(t, samples) {
125
    t = this.scaleFactor_ * t;
126
    let k = Math.floor(t);
127
    let m = [this.getTangent_(k, samples), this.getTangent_(k + 1, samples)];
128
    let p = [this.getClippedInput_(k, samples),
129
      this.getClippedInput_(k + 1, samples)];
130
    t -= k;
131
    let t2 = t * t;
132
    let t3 = t * t2;
133
    return (2 * t3 - 3 * t2 + 1) *
134
      p[0] + (t3 - 2 * t2 + t) *
135
      m[0] + (-2 * t3 + 3 * t2) *
136
      p[1] + (t3 - t2) * m[1];
137
  }
138
139
  /**
140
   * @param {number} t The index to interpolate.
141
   * @param {Array|TypedArray} samples the original array.
142
   * @return {number} The interpolated value.
143
   */
144
  sinc(t, samples) {
145
    t = this.scaleFactor_ * t;
146
    let k = Math.floor(t);
147
    let ref = k - this.sincFilterSize_ + 1;
148
    let ref1 = k + this.sincFilterSize_;
149
    let sum = 0;
150
    for (let n = ref, j = ref;
151
        ref <= ref1 ? j <= ref1 : j >= ref1; 
152
        n = ref <= ref1 ? ++j : --j) {
153
      sum += this.kernel_(t - n) * this.getClippedInput_(n, samples);
154
    }
155
    return sum;
156
  }
157
158
  /**
159
   * @param {number} k The scaled index to interpolate.
160
   * @param {Array|TypedArray} samples the original array.
161
   * @return {number} The tangent.
162
   * @private
163
   */
164
  getTangent_(k, samples) {
165
    return this.tangentFactor_ *
166
      (this.getClippedInput_(k + 1, samples) -
167
        this.getClippedInput_(k - 1, samples)) / 2;
168
  }
169
170
  /**
171
   * @param {number} t The scaled index to interpolate.
172
   * @param {Array|TypedArray} samples the original array.
173
   * @return {number} The interpolated value.
174
   * @private
175
   */
176
  getClippedInput_(t, samples) {
177
    if ((0 <= t && t < this.length_)) {
178
      return samples[t];
179
    }
180
    return samples[this.clip_(t, this.length_)];
181
  }
182
}
183
184
// Sinc functions
185
186
/**
187
 * The default window function.
188
 * @param {number} x The sinc signal.
189
 * @return {number}
190
 * @private
191
 */
192
function gaussianWindow_(x) {
193
  return Math.exp(-x * x);
194
}
195
196
/**
197
 * @param {Function} window The window function.
198
 * @return {Function}
199
 * @private
200
 */
201
function sincKernel_(window) {
202
  return function(x) { return sinc_(x) * window(x); };
203
}
204
205
/**
206
 * @param {number} x The sinc signal.
207
 * @return {number}
208
 * @private
209
 */
210
function sinc_(x) {
211
  if (x === 0) {
212
    return 1;
213
  }
214
  return Math.sin(Math.PI * x) / (Math.PI * x);
215
}
216
217
// Clip functions
218
219
/**
220
 * @param {number} t The scaled index
221
 * @param {number} n The size of the original array
222
 * @return {number}
223
 * @private
224
 */
225
function clipClamp_(t, n) {
226
  return Math.max(0, Math.min(t, n - 1));
227
}
228
229
/**
230
 * @param {number} t The scaled index
231
 * @param {number} n The size of the original array
232
 * @return {number}
233
 * @private
234
 */
235
function clipPeriodic_(t, n) {
236
  t = t % n;
237
  if (t < 0) {
238
    t += n;
239
  }
240
  return t;
241
}
242
243
/**
244
 * @param {number} t The scaled index
245
 * @param {number} n The size of the original array
246
 * @return {number}
247
 * @private
248
 */
249
function clipMirror_(t, n) {
250
  let period = 2 * (n - 1);
251
  t = clipPeriodic_(t, period);
252
  if (t > n - 1) {
253
    t = period - t;
254
  }
255
  return t;
256
}
257